#ifdef LINUX

#include "Network/UDPServer.h"

#include "Containers/Tuple.h"

#include "Async/PackagedTask.h"

#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <fcntl.h>

TThreadSafeSharedPtr<CUDPServer> CUDPServer::Create(const SIPAddress& IP, uint16_t Port)
{
    //create a new socket and connect to host
    auto NewSocket = socket(AF_INET, SOCK_DGRAM, 0);
    if (NewSocket < 0)
    {
        return nullptr;
    }

    sockaddr_in ServerAddress;
    ServerAddress.sin_family = AF_INET;
    ServerAddress.sin_port = htons(Port);
    CString8 IPStr = IP.ToString().ToUtf8String();
    ServerAddress.sin_addr.s_addr = inet_addr(IPStr.CStr());

    if (bind(NewSocket, (sockaddr*)&ServerAddress, sizeof(ServerAddress)) < 0)
    {
        return nullptr;
    }

    int* NewSocketPtr = new int(NewSocket);
    return WH::MakeThreadSafeSharedPtr<CUDPServer>(NewSocketPtr);
}

CUDPServer::CUDPServer(void* InPlatformSocket)
{
    PlatformSocket = InPlatformSocket;

    //set socket to non-blocking
    int Flags = fcntl(*(int*)PlatformSocket, F_GETFL, 0);
    fcntl(*(int*)PlatformSocket, F_SETFL, Flags | O_NONBLOCK);

    StartReciveTask();

}

CUDPServer::~CUDPServer()
{
    close(*(int*)PlatformSocket);
    delete (int*)PlatformSocket;
}

void CUDPServer::StartReciveTask()
{
    using ReceivedType=TTuple<TVector<uint8_t> /*recived data*/ ,SIPAddress /*IP*/, uint16_t /*Port*/>;

    std::shared_ptr<CPackagedTask<ReceivedType>> ReciveTask;

    ReciveTask =WH::CreateAsyncTask<ReceivedType>([this](bool&) 
        ->ReceivedType
    {
        static const uint16_t ReciveBufferSize = 1024;

        TVector<uint8_t> Data;
        Data.Resize(ReciveBufferSize);
        uint32_t TotalRecivedByteCount = 0;

        while (true)
        {
            sockaddr_in ClientAddress;
            socklen_t ClientAddressSize = sizeof(ClientAddress);

            int32_t RevicedByteCount= recvfrom(*(int*)PlatformSocket, Data.Data(), Data.Num(), 0, (sockaddr*)&ClientAddress, &ClientAddressSize);

            if(RevicedByteCount==0)
            {
                //connection closed
                //bIsConnected.store(false);
                return ReceivedType();
            }


            if(RevicedByteCount<0)
            {
                switch(errno)
                {
                    case EAGAIN:
                    {
                        //no data
                        return ReceivedType();
                    }

                    default:
                    {
                        //error
                        //bIsConnected.store(false);
                        return ReceivedType();
                    }
                }
            }
            else
            {
                TotalRecivedByteCount+=RevicedByteCount;
                if(TotalRecivedByteCount==Data.Num())
                {
                    //buffer too small
                    Data.Resize(Data.Num() * 2);
                    continue;
                }
            }

            uint8_t IP[4];
            IP[0] = ClientAddress.sin_addr.s_addr & 0xFF;
            IP[1] = (ClientAddress.sin_addr.s_addr >> 8) & 0xFF;
            IP[2] = (ClientAddress.sin_addr.s_addr >> 16) & 0xFF;
            IP[3] = (ClientAddress.sin_addr.s_addr >> 24) & 0xFF;

            return WH::MakeTuple(Data, SIPAddress(IP[0],IP[1],IP[2],IP[3]), ntohs(ClientAddress.sin_port));
        }

        assert(false&&"should not reach here");
        return ReceivedType();
    });

    ReciveTask->ThenInMainThread<void>(
        [this,ReciveTask](bool&)
        {
            ReceivedType& InReceived=ReciveTask->Task->GetResult();
            if(InReceived.First.Num()>0)
            {
                this->OnDataReceived.ExecuteIfBound(InReceived.First, InReceived.Second, InReceived.Third);
            }

            this->StartReciveTask();
        }
        );
    
    ReciveTask->Task->AddToSchedule();
}


#endif